#define _USE_MATH_DEFINES // For MS VS C++ compilers
#include <cmath>
#include "scene_object.h"


#include <QFile>
#include <QTextStream>
#include <QVector3D>
#include <QDebug>

// This is a "helper function" that takes a string list of tokens and appends
// the float values the tokens contain to the vector of float values given as
// the second argument to the function.
// If the conversion from string to float fails the value NaN is appended instead.
// The function returns the number of tokens appended
static int parseToFloatVector(const QStringList & tokens, QVector<float> & container)
{
    int itok=0;
    foreach (QString tok, tokens) {
        bool ok;
        float val=tok.toFloat(&ok);
        container.push_back( ok?val:nanf("") );
        itok++;
    }
    return itok;
}

// This is a "helper function" that parses a string list of face indices according to the
// obj specification into vertex, texture and normal indices and appends the results to the
// respective argument vectors of this function.
//
// Because obj files can actually have have quads we work in two steps, the second step splits
// a quad into two triangles. Polygons could be handled this way too - but we are not doing that now.
//
// Note: This function handles four different cases with the help of a matching regular expression, namely:
// f V
// f V/T
// f V/T/N
// f V//N
static int parseToIndexVectors(const QStringList & tokens, QVector<quint32> & vIndex, QVector<quint32> & tIndex, QVector<quint32> & nIndex)
{
    int itok=0;
    const int ntokens=tokens.size();
    if (ntokens>4) {
        qWarning() << "Can not read general polygons, ignoring face with" << ntokens << "indices";
        return 0;
    }
    int ivv[4]={-1,-1,-1,-1},itv[4]={-1,-1,-1,-1},inv[4]={-1,-1,-1,-1};
    foreach (QString tok, tokens) {
        QRegExp rx("(\\d+)(/?)(\\d*)(/?)(\\d*)");
        int pos=rx.indexIn(tok);
        if (pos>-1) {
            int capCount=rx.captureCount();
            if (capCount==5) {
                QStringList capTexts=rx.capturedTexts();
                bool ok;
                int iv=capTexts.at(1).toInt(&ok)-1; if (ok) ivv[itok]=iv;
                int it=capTexts.at(3).toInt(&ok)-1; if (ok) itv[itok]=it;
                int in=capTexts.at(5).toInt(&ok)-1; if (ok) inv[itok]=in;
            }
        }
        itok++;
    }
    vIndex.push_back(ivv[0]); if (itv[0]>-1) tIndex.push_back(itv[0]); if (inv[0]>-1) nIndex.push_back(inv[0]);
    vIndex.push_back(ivv[1]); if (itv[1]>-1) tIndex.push_back(itv[1]); if (inv[1]>-1) nIndex.push_back(inv[1]);
    vIndex.push_back(ivv[2]); if (itv[2]>-1) tIndex.push_back(itv[2]); if (inv[2]>-1) nIndex.push_back(inv[2]);
    // If we have 4 tokens we assume a quad with ccw orientation, i.e. {1,2,3,4} splits into {1,2,3} and {1,3,4}
    if (ntokens==4) {
        vIndex.push_back(ivv[0]); if (itv[0]>-1) tIndex.push_back(itv[0]); if (inv[0]>-1) nIndex.push_back(inv[0]);
        vIndex.push_back(ivv[2]); if (itv[2]>-1) tIndex.push_back(itv[2]); if (inv[2]>-1) nIndex.push_back(inv[2]);
        vIndex.push_back(ivv[3]); if (itv[3]>-1) tIndex.push_back(itv[3]); if (inv[3]>-1) nIndex.push_back(inv[3]);
    }
    // If we wanted to split polygons we could do that here. We could add a Vertex in the center of mass and
    // create a triangle fan, that would work well as long as the polygons are convex.
    // Seems a bit outlandish for a class.
    return itok;
}

SceneObject::SceneObject()
    : _fileName(QString::null)
    , _vertices()
    , _numOfVertexComponents(0)
    , _normals()
    , _textures()
    , _numOfTextureComponents(0)
    , _vertexIndices()
    , _normalIndices()
    , _textureIndices()
    , _vertexBuffer(QOpenGLBuffer::VertexBuffer)
    , _normalBuffer(QOpenGLBuffer::VertexBuffer)
    , _indexBuffer(QOpenGLBuffer::IndexBuffer)
{
}

SceneObject::~SceneObject()
{
    _vertexBuffer.destroy();
    _normalBuffer.destroy();
    _indexBuffer.destroy();
}

void SceneObject::init()
{
    _ambient = QVector3D( 0.05f, 0.2f, 0.05f );
    _diffuse = QVector3D( 0.3f, 0.5f, 0.3f );
    _specular = QVector3D( 0.6f, 0.6f, 0.6f );
    _shininess = 20.0f;
}


void SceneObject::setupLighting(Light light)
{
//    _shaderProgram.bind();

//     // Set light uniforms.
//     // Note how we handle structures as uniforms here and
//     // correspondingly in the vertex shader
//     // Set light uniforms
//     _shaderProgram.setUniformValue("light.position", light._position);
//     _shaderProgram.setUniformValue("light.intensity", light._intensity);

//     /// Set material uniforms
//     _shaderProgram.setUniformValue("material.Ka", _ambient);
//     _shaderProgram.setUniformValue("material.Kd", _diffuse);
//     _shaderProgram.setUniformValue("material.Ks", _specular);
//     _shaderProgram.setUniformValue("material.shininess", _shininess);
}


void SceneObject::createBuffers()
{
    // Create a static buffer the for vertex data
    _vertexBuffer.create();
    // Set usage pattern to Static Draw, (the data won't change)
    _vertexBuffer.setUsagePattern( QOpenGLBuffer::StaticDraw );
    // Bind the buffer to the current OpenGL context
    _vertexBuffer.bind();
    // Copy the data to the buffer
    _vertexBuffer.allocate( vertices().constData(), vertices().size() * sizeof( float ) );

    _normalBuffer.create();
    _normalBuffer.setUsagePattern( QOpenGLBuffer::StaticDraw );
    _normalBuffer.bind();
    _normalBuffer.allocate( normals().constData(), normals().size() * sizeof( float ) );

    // Create index buffer
    _indexBuffer.create();
    _indexBuffer.setUsagePattern( QOpenGLBuffer::StaticDraw );
    _indexBuffer.bind();
    _indexBuffer.allocate( vertexIndices().constData(), vertexIndices().size() * sizeof( int ) );
}

void SceneObject::createShaderProgram()
{
//    _shaderProgram.removeAllShaders();
//    qDebug() << "after remove";
//    QByteArray version = "#version 450";

//    qDebug() << "using shader version:" << version;
//    version += "\n";


//        QFile vtFile(":/ads_fragment.vert");
//        vtFile.open((QIODevice::ReadOnly | QIODevice::Text));
//        QFile fsFile (":/ads_fragment.frag");
//        fsFile.open((QIODevice::ReadOnly | QIODevice::Text));

//        if ( !_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, vtFile.fileName())) {
//            qDebug() << "Error in vertex shader:" << _shaderProgram.log();
//            exit(1);
//        }

//        // Compile fragment shader
//        if ( !_shaderProgram.addShaderFromSourceFile( QOpenGLShader::Fragment, fsFile.fileName())) {
//            qDebug() << "Error in fragment shader:" << _shaderProgram.log();
//            exit(1);
//        }
////        // Compile vertex shader
////        if ( !_shaderProgram.addShaderFromSourceCode( QOpenGLShader::Vertex, version+vtFile.readAll())) {
////            qDebug() << "Error in vertex shader:" << _shaderProgram.log();
////            exit(1);
////        }

////        // Compile fragment shader
////        if ( !_shaderProgram.addShaderFromSourceCode( QOpenGLShader::Fragment, version+fsFile.readAll())) {
////            qDebug() << "Error in fragment shader:" << _shaderProgram.log();
////            exit(1);
////        }


//    _shaderProgram.bindAttributeLocation("vertexPosition", 0);
//    _shaderProgram.bindAttributeLocation("vertexNormal", 1);


//    // Link the shaders together into a program
//    if ( !_shaderProgram.link() ) {
//        qDebug() << "Error linking shader program:" << _shaderProgram.log();
//        exit(1);
//    }
}


void SceneObject::draw()
{
//    _shaderProgram.bind();

//    _indexBuffer.bind();

//    glDrawElements(GL_TRIANGLES, vertexIndices().size(), GL_UNSIGNED_INT, 0);

//    _vertexBuffer.release();
//    _indexBuffer.release();
        qDebug() << "In Draw!";
    // Draws a spiral
    const float nbSteps = 200.0;
    glBegin(GL_QUAD_STRIP);
    for (float i=0; i<nbSteps; ++i)
      {
        float ratio = i/nbSteps;
        float angle = 21.0*ratio;
        float c = cos(angle);
        float s = sin(angle);
        float r1 = 1.0 - 0.8*ratio;
        float r2 = 0.8 - 0.8*ratio;
        float alt = ratio - 0.5;
        const float nor = .5;
        const float up = sqrt(1.0-nor*nor);
        glColor3f(1.0-ratio, 0.2f , ratio);
        glNormal3f(nor*c, up, nor*s);
        glVertex3f(r1*c, alt, r1*s);
        glVertex3f(r2*c, alt+0.05, r2*s);
      }
    glEnd();
}


void SceneObject::readObjFile(const QString fileName)
{
    if (fileName.isEmpty()) return;
    _fileName=fileName;

// IMPLEMENT THE READ FUNCTION FROM HERE
    // Create a QFile object with our file name
    QFile file(_fileName);
    // Open the file (read only)
    if (file.open((QIODevice::ReadOnly))) {
        // Create a text stream object to access the file
        QTextStream inStream(&file);
        // Create a loop to read from the stream until the stream ends
        int lineCount=0;
        while ( !inStream.atEnd())
        {
            // Read a line from the stream, enable the debug statement if this feels awkward
            QString line = inStream.readLine();
            lineCount++;
            // qDebug() << line;
            // Check that it isn't empty
            if (line.isEmpty()) continue;
            // Split the line into "Tokens" - continues character blurbs
            QStringList tokens = line.split(" ",QString::SkipEmptyParts);
            int nTokens=tokens.count();
            // Check that you have at least one token
            if (!nTokens) continue;
            // Take the first token as the keyword, one of "v","vn","vt","f", ignore "#","g" and "o"
            QString keyString=tokens.takeFirst();
//            qDebug() << "read key:" << keyString ;
            // Now check if the keyString is "v",
            // If it is call the parse function that will parse the rest of the tokens into
            // the array of vertices (_vertices)
            if (0==keyString.compare("v",Qt::CaseInsensitive))
            {
                int nparsed=parseToFloatVector(tokens,_vertices);
                if (!nparsed) {
                    qWarning() << "could not parse vertices from " << tokens;
                }
                else {
                    if (_numOfVertexComponents==0) {
                        _numOfVertexComponents=nparsed;
                    }
                    else {
                        if (_numOfVertexComponents!=nparsed) {
                            qWarning() << "Number of Vertex components inconistent\n    was" << _numOfVertexComponents
                                       << "\n    found: " << nparsed << "\nin line: " << lineCount
                                       << "\n    This file can not be read properly!";
                        }
                    }
                }
            }
            // Repeat for normals
            else if (0==keyString.compare("vn",Qt::CaseInsensitive))
            {
                int nparsed=parseToFloatVector(tokens,_normals);
                if (!nparsed) {
                    qWarning() << "could not parse normals from " << tokens;
                }
            }
            // Repeat for textures
            else if (0==keyString.compare("vt",Qt::CaseInsensitive))
            {
                int nparsed=parseToFloatVector(tokens,_textures);
                if (!nparsed) {
                    qWarning() << "could not parse textures from " << tokens;
                }
                else {
                    if (_numOfTextureComponents==0) {
                        _numOfTextureComponents=nparsed;
                    }
                    else {
                        if (_numOfTextureComponents!=nparsed) {
                            qWarning() << "Number of Texture components inconistent\n    was" << _numOfTextureComponents
                                       << "\n    found: " << nparsed << "\nin line: " << lineCount
                                       << "\n    This file can not be read properly!";
                        }
                    }
                }
            }
            // Repeat for faces but call a different parse function
            else if (0==keyString.compare("f",Qt::CaseInsensitive))
            {
                /*int nparsed=*/parseToIndexVectors(tokens,_vertexIndices,_textureIndices,_normalIndices);
            }
            else if ((0==keyString.compare("#"))||(0==keyString.compare("g",Qt::CaseInsensitive))||(0==keyString.compare("o",Qt::CaseInsensitive))) {
//                qDebug() << "Ignoring KeyType:" << keyString;
            }
            else
            {
                qWarning() << "Unknown KeyType:" << keyString << " not one of f, vn, vt, v, #, g, o";
            }
        }
    }
}

void SceneObject::debugOutput(const int level)
{
    qDebug() << "                Name: " <<  _fileName;
    qDebug() << "            Vertices: " <<  numVertices();
    qDebug() << "   Vertex Components: " <<  numVertexComponents();
    qDebug() << "             Normals: " <<  numNormals();
    qDebug() << " Texture Coordinates: " <<  numTextures();
    qDebug() << "  Texture Components: " <<  numTextureComponents();
    qDebug() << " Face Vertex Indices: " <<  numVertexIndices();
    qDebug() << "Face Texture Indices: " <<  numTextureIndices();
    qDebug() << " Face Normal Indices: " <<  numNormalIndices();

    if (level>0) {
        for (int i=0;i<_vertices.size();i+=3) {
            qDebug() << "Vertex: " << (i/3) << (_vertices.at(i)) << (_vertices.at(i+1))  << (_vertices.at(i+2));
        }
        for (int i=0;i<_normals.size();i+=3) {
            qDebug() << "Normal: " << (i/3) << (_normals.at(i)) << (_normals.at(i+1))  << (_normals.at(i+2));
        }
        for (int i=0;i<_textures.size();i+=3) {
            qDebug() << "Texture: " << (i/3) << (_textures.at(i)) << (_textures.at(i+1))  << (_textures.at(i+2));
        }
        for (int i=0;i<_vertexIndices.size();i+=3) {
            qDebug() << "VIndex: " << (i/3) << (_vertexIndices.at(i)) << (_vertexIndices.at(i+1))  << (_vertexIndices.at(i+2));
        }
        for (int i=0;i<_textureIndices.size();i+=3) {
            qDebug() << "TIndex: " << (i/3) << (_textureIndices.at(i)) << (_textureIndices.at(i+1))  << (_textureIndices.at(i+2));
        }
        for (int i=0;i<_normalIndices.size();i+=3) {
            qDebug() << "NIndex: " << (i/3) << (_normalIndices.at(i)) << (_normalIndices.at(i+1))  << (_normalIndices.at(i+2));
        }
    }
}

void SceneObject::calculateNormalsFromFaces()
{
    QVector<int> faceCount(numVertices());
    _normals.clear();
    _normals.resize(numVertices()*3);
    const int nstride=_numOfVertexComponents;
    for (int i=0;i<_vertexIndices.size();i+=3) {
        int ip1=_vertexIndices[i];
        int ip2=_vertexIndices[i+1];
        int ip3=_vertexIndices[i+2];

        QVector3D v21(_vertices[nstride*ip2]-_vertices[nstride*ip1],
                      _vertices[nstride*ip2+1]-_vertices[nstride*ip1+1],
                      _vertices[nstride*ip2+2]-_vertices[nstride*ip1+2]);

        QVector3D v31(_vertices[nstride*ip3]-_vertices[nstride*ip1],
                      _vertices[nstride*ip3+1]-_vertices[nstride*ip1+1],
                      _vertices[nstride*ip3+2]-_vertices[nstride*ip1+2]);

        QVector3D fn = QVector3D::crossProduct(v21,v31);
        _normals[3*ip1]+=fn[0];
        _normals[3*ip1+1]+=fn[1];
        _normals[3*ip1+2]+=fn[2];

        _normals[3*ip2]+=fn[0];
        _normals[3*ip2+1]+=fn[1];
        _normals[3*ip2+2]+=fn[2];

        _normals[3*ip3]+=fn[0];
        _normals[3*ip3+1]+=fn[1];
        _normals[3*ip3+2]+=fn[2];

        faceCount[ip1]++;
        faceCount[ip2]++;
        faceCount[ip3]++;
    }
    for (int i=0;i<_normals.size();i+=3) {
        if (faceCount[i/3]!=0) {
            QVector3D vna(_normals[i]/faceCount[i/3],
                          _normals[i+1]/faceCount[i/3],
                          _normals[i+2]/faceCount[i/3]);
            vna.normalize();
            _normals[i]=vna[0];
            _normals[i+1]=vna[1];
            _normals[i+2]=vna[2];
        }
    }
}
